#import('dart:html');

void main() {
  new ElasticCollisionDemo();
}

class ElasticCollisionDemo {

  List<Ball> ballz;
  CanvasRenderingContext2D ctx;
  CanvasElement canvas;
  List colors = const["green", "orange", "red", "yellow", "blue"];
  Board board; 
  
  ElasticCollisionDemo() {    
    canvas = document.query("#canvas");
    ctx = canvas.getContext("2d");        
    board = new Board(0, 0, 600.0, 480.0);
    ballz = new List<Ball>();    
    int k=0;
    while(k<30) {
      num r = rand(20.0)+3;
      num x = rand(board.x2-board.x1);
      num y = rand(board.y2-board.y1);
      num vx = randSpeed();
      num vy = randSpeed();      
      String color=  colors[rand(colors.length.toDouble()).toInt()];
      
      Ball ball = new Ball( r, x, y, vx, vy, color );
      if(checkValid(ball)) {
        ballz.add(ball);
        k++;
      }
    }
    drawScene(0);
  }
  
  double rand(double max) => Math.random()*max;
  
  num randSpeed() => Math.random() < 0.5 ? -Math.random()*2 : Math.random()*2;
  
  bool checkValid(Ball ball) {
    if(!board.inside(ball))
      return false;
    for(Ball t in ballz) {
      if(t.distanceTo(ball) < ball.r + t.r)
        return false;
    }
    return true;
  }
  
  bool drawScene(int time) { 
        
    ctx.clearRect(board.x1-10, board.y1-10, board.x2+10, board.y2+10);    
    
    for(Ball t in ballz) {
      t.move();
      t.checkWall(board);               
      drawBall(t);      
    }    
   
    for(int i = 0; i<ballz.length; i++) {
      for(int j = i+1; j<ballz.length;j++) {
        ballz[i].checkCollision(ballz[j]); 
      }
    } 
    document.window.requestAnimationFrame(drawScene);
  }
  
  void drawBall(Ball ball) {
    ctx.beginPath();
    ctx.lineWidth = 2;
    ctx.fillStyle = ball.color;
    ctx.strokeStyle = ball.color;
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
    ctx.fill();
    ctx.closePath();
    ctx.stroke();
  }  
}

class Board {
  num x1,y1,x2,y2;
  Board(this.x1, this.y1, this.x2, this.y2);
  bool inside(Ball ball) => ball.x-ball.r > x1 && ball.x+ball.r < x2 && ball.y-ball.r > y1 && ball.y+ball.r < y2;  
}

class Ball {
  
  final double r;
  double vX;
  double vY;
  double x;
  double y;
  String color;
  
  Ball(this.r,  this.x, this.y, this.vX, this.vY, this.color);
  
  void checkWall(Board board) {
    if(x-r < board.x1) { 
      vX= -vX;
      x = board.x1+r;
    } else if(x+r > board.x2) {
      vX= -vX;
      x = board.x2-r;      
    }
    if(y-r < board.y1) { 
      vY= -vY;
      y = board.y1+r;
    } else if(y+r > board.y2) {
      vY= -vY;
      y = board.y2-r;      
    }    
  }
  
  double distanceTo(Ball ball) => Math.sqrt((ball.x-x)*(ball.x-x) + (ball.y-y)*(ball.y-y));   
 
  void checkCollision(Ball t) {
      if(this==t)
        return;
      // collision?     
      if(distanceTo(t) <= (r+t.r)) {
        // formula: v1new = (v1*(m1-m2)+2*m2*v2)/(m1+m2)    
        double m1 = r*0.1;
        double m2 = t.r*0.1;
        // cannot change the first ball's speed in place. it will be used in other equations.
        double nX = (vX * (m1 - m2) + (2 * m2 * t.vX)) / (m1+m2);
        double nY = (vY * (m1 - m2) + (2 * m2 * t.vY)) / (m1+m2);
        t.vX = (t.vX * (m2 - m1) + (2 * m1 * vX)) / (m1+m2);
        t.vY = (t.vY * (m2 - m1) + (2 * m1 * vY)) / (m1+m2);
        vX = nX;
        vY = nY;
        move();
        t.move();
    }
  } 
  
  void move() { 
    x = x+vX; 
    y = y+vY; 
  }        
  
}
